#include "debugwindow.h"
#include "ui_debugwindow.h"

#include <QStringListModel>
#include <QRegExp>
#include <QtDebug>

#define RADIO_STATUS_LOOPBACK 0
#define RADIO_STATUS_SEND_LORA 1
#define RADIO_STATUS_GET_DEVICE_ID 2
#define RADIO_STATUS_SET_DEVICE_ID 3

#define CMD_LOOPBACK 0x00
#define ACK_LOOPBACK (CMD_LOOPBACK | 0x80)
#define CMD_RECV_LORA 0x01
#define ACK_RECV_LORA (CMD_RECV_LORA | 0x80)
#define CMD_SEND_LORA 0x02
#define ACK_SEND_LORA (CMD_SEND_LORA | 0x80)
#define CMD_GET_DEVICEID 0x03
#define ACK_GET_DEVICEID (CMD_GET_DEVICEID | 0x80)
#define CMD_SET_DEVICEID 0x04
#define ACK_SET_DEVICEID (CMD_SET_DEVICEID | 0x80)
#define CMD_KEY_EVENT 0x05
#define ACK_KEY_EVENT (CMD_KEY_EVENT | 0x80)
#define CMD_LOG 0x06
#define ACK_LOG (CMD_LOG | 0x80)

#define COLOR_LOOPBACK 0xFFAAFFAA
#define COLOR_LORA 0xFFFFAAAA
#define COLOR_DEVICE_ID 0xFFFFFFAA
#define COLOR_KEY_EVENT 0xFFFFAAFF
#define COLOR_LOG 0xFFAAAAFF
#define COLOR_UNKNOWN 0xFFAAAAAA

DebugWindow::DebugWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::DebugWindow),
    mpSerialServer(nullptr)
{
    ui->setupUi(this);

    connect(ui->btn_send, & QPushButton::pressed, [this](){onSendClicked();});
    connect(ui->btn_connect, & QPushButton::pressed, [this](){onConnectClicked();});
    connect(ui->btn_clear, & QPushButton::pressed, [this](){onClearClicked();});

    connect(ui->rbtn_loopback, & QPushButton::pressed, [this](){onRadioLoopback();});
    connect(ui->rbtn_send_lora, & QPushButton::pressed, [this](){onRadioSendLora();});
    connect(ui->rbtn_set_device_id, & QPushButton::pressed, [this](){onRadioSetDeviceId();});
    connect(ui->rbtn_get_device_id, & QPushButton::pressed, [this](){onRadioGetDeviceId();});

    connect(&mConversationModel, & QAbstractListModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last){
        Q_UNUSED(parent);
        Q_UNUSED(last);
        Q_UNUSED(first);
        ui->list_conversation->scrollToBottom();
    }, Qt::QueuedConnection);

    ui->list_conversation->setModel(&mConversationModel);
    ui->list_conversation->setItemDelegate(&mConversationDelegate);

    mPortModel.setOnPortLoadedListener([this](QList<QSerialPortInfo>& ports){onPortLoaded(ports);});
    ui->cb_ports->setModel(&mPortModel);
    mPortModel.startMonitor(winId());
}

DebugWindow::~DebugWindow() {
    mPortModel.stopMonitor();
    delete ui;
}

void DebugWindow::onPortLoaded(QList<QSerialPortInfo>& ports) {
    int portCount = ui->cb_ports->count();
    qDebug() << "port count: " << portCount;

    // get original index
    int portIndex = -1;
    if (mpSerialServer) {
        for (int i=0; i<ports.size(); i++) {
            auto& port = ports[i];
            if (mpSerialServer->portName() == port.portName()) {
                portIndex = i;
                break;
            }
        }
        if (portIndex < 0) {
            // do disconnect
            mpSerialServer->close();
            delete mpSerialServer;
            mpSerialServer = nullptr;
        }
    }

    // update index
    ui->cb_ports->setCurrentIndex(portIndex < 0 ? 0 : portIndex);

    updateUi();
    ui->btn_connect->setEnabled(portCount > 0);
}

void DebugWindow::updateUi() {
    int portCount = ui->cb_ports->count();
    ui->btn_connect->setEnabled(portCount > 0);

    if (mpSerialServer) {
        ui->btn_connect->setText("Disconnect");
        ui->cb_ports->setEnabled(false);
        ui->btn_send->setEnabled(true);
        ui->edit_data->setEnabled(true);
    } else {
        ui->btn_connect->setText("Connect");
        ui->cb_ports->setEnabled(true);
        ui->btn_send->setEnabled(false);
        ui->edit_data->setEnabled(false);
    }
}

void DebugWindow::onSendClicked() {
    qDebug() << "clicked";
    QByteArray bytes = getInputText();

    switch (mRadioStatus) {
    case RADIO_STATUS_SEND_LORA:
        onSendLora(bytes);
        break;
    case RADIO_STATUS_GET_DEVICE_ID:
        onSendGetDevice(bytes);
        break;
    case RADIO_STATUS_SET_DEVICE_ID:
        onSendSetDevice(bytes);
        break;
    case RADIO_STATUS_LOOPBACK:
    default:
        onSendLoopback(bytes);
    }
}

void DebugWindow::onConnectClicked() {
    if (mpSerialServer) {
        // do disconnect
        mpSerialServer->close();
        delete mpSerialServer;
        mpSerialServer = nullptr;
    } else {
        // do connect
        QString portName = ui->cb_ports->currentData(Qt::UserRole).toString();
        qDebug() << "current port name: " << portName;
        if (portName.isEmpty()) {
            qDebug() << "Invalid port name. Ignore";
            return;
        }

        mpSerialServer = new SerialServer(portName);
        connect(mpSerialServer, &SerialServer::received,
                this,
                [this](QByteArray buff){
                    onUsbReceived(buff);
                }, Qt::QueuedConnection);

        mpSerialServer->listen();
        qDebug() << "Connect to " << portName;
    }

    updateUi();
}

void DebugWindow::onClearClicked() {
    mConversationModel.clear();
}

void DebugWindow::onRadioLoopback() {
    mRadioStatus = RADIO_STATUS_LOOPBACK;
}

void DebugWindow::onRadioSendLora() {
    mRadioStatus = RADIO_STATUS_SEND_LORA;
}

void DebugWindow::onRadioSetDeviceId() {
    mRadioStatus = RADIO_STATUS_SET_DEVICE_ID;
}

void DebugWindow::onRadioGetDeviceId() {
    mRadioStatus = RADIO_STATUS_GET_DEVICE_ID;
}

void DebugWindow::onSendLoopback(QByteArray& bytes) {
    // show sent msg
    QString display;
    display.append("[Loopback]\n");
    display.append(bytesToString(bytes));
    mConversationModel.add(display, COLOR_LOOPBACK, true);

    // send to session
    if (mpSerialServer) {
        bytes.prepend(static_cast<char>(CMD_LOOPBACK));
        mpSerialServer->send(bytes);
    }
}

void DebugWindow::onSendLora(QByteArray& bytes) {
    // make send bytes
    QString targetStr = ui->edit_lora_target->text();
    QByteArray targetBytes = DebugWindow::parserInputText(targetStr);
    if (targetBytes.size() < 4) {
        targetBytes.prepend(4 - targetBytes.size(), 0);
    }

    // show sent msg
    QString display;
    display.append("[Send Lora][");
    display.append(bytesToString(targetBytes));
    display.append("]\n");
    display.append(bytesToString(bytes));
    mConversationModel.add(display, COLOR_LORA, true);

    // send to session
    if (mpSerialServer) {
        bytes.prepend(targetBytes); // target device id
        bytes.prepend(static_cast<char>(CMD_SEND_LORA));
        mpSerialServer->send(bytes);
    }
}

void DebugWindow::onSendSetDevice(QByteArray& bytes) {
    // show sent msg
    bytes.resize(4);
    QString display;
    display.append("[Set ID]\n");
    display.append(bytesToString(bytes));
    mConversationModel.add(display, COLOR_DEVICE_ID, true);

    // send to session
    if (mpSerialServer) {
        bytes.prepend(static_cast<char>(CMD_SET_DEVICEID));
        mpSerialServer->send(bytes);
    }
}

void DebugWindow::onSendGetDevice(QByteArray& bytes) {
    // show sent msg
    bytes.clear();
    QString display;
    display.append("[Get ID]");
    mConversationModel.add(display, COLOR_DEVICE_ID, true);

    // send to session
    if (mpSerialServer) {
        bytes.prepend(static_cast<char>(CMD_GET_DEVICEID));
        mpSerialServer->send(bytes);
    }
}

QByteArray DebugWindow::getInputText() {
    QString text = ui->edit_data->toPlainText();
    return parserInputText(text);
}

QByteArray DebugWindow::parserInputText(QString& input) {
    QString text = input.trimmed();
    text.replace(QRegExp("[\\s]"), "");
    text = text.toUpper();
    QRegExp reg("[0-9A-F]+");
    if (!reg.exactMatch(text)) {
        qDebug() << "input not number";
        return QByteArray();
    }
    if (text.size() % 2) {
        text = "0" + text;
    }

    bool ok;
    QByteArray bytes;
    for (int i=0; i<text.size(); i+=2) {
        bytes.append(text.mid(i, 2).toInt(&ok, 16));
    }
    return bytes;
}

void DebugWindow::onUsbReceived(QByteArray& bytes) {
    //qDebug() << "received: " << buff;
    if (bytes.size() < 1) {
        return;
    }

    switch (static_cast<unsigned char>(bytes[0])) {
    case CMD_RECV_LORA:
        onRecvLora(bytes);
        break;
    case ACK_SEND_LORA:
        onRecvAckSendLora(bytes);
        break;
    case ACK_GET_DEVICEID:
        onRecvAckGetDeviceId(bytes);
        break;
    case ACK_SET_DEVICEID:
        onRecvAckSetDeviceId(bytes);
        break;
    case CMD_KEY_EVENT:
        onRecvKeyEvent(bytes);
        break;
    case CMD_LOG:
        onRecvLog(bytes);
        break;
    case ACK_LOOPBACK:
        onRecvLoopback(bytes);
        break;
    default:
        onRecvUnknown(bytes);
    }
}

void DebugWindow::onRecvLora(QByteArray& bytes) {
    QByteArray from = bytes.mid(1, 4);
    QByteArray data = bytes.mid(5);

    QString content;
    content.append("[Recv Lora][");
    content.append(bytesToString(from));
    content.append("]\n");
    content.append(bytesToString(data));
    mConversationModel.add(content, COLOR_LORA, false);
}

void DebugWindow::onRecvAckSendLora(QByteArray& bytes) {
    Q_UNUSED(bytes);

    QString content;
    content.append("[Send Lora Done]");
    mConversationModel.add(content, COLOR_LORA, false);
}

void DebugWindow::onRecvAckGetDeviceId(QByteArray& bytes) {
    QByteArray deviceId = bytes.mid(1);

    // show received
    QString content;
    content.append("[Get ID Done]\n");
    content.append(bytesToString(deviceId));
    mConversationModel.add(content, COLOR_DEVICE_ID, false);
    ui->label_device_id->setText(bytesToString(deviceId));
}

void DebugWindow::onRecvAckSetDeviceId(QByteArray& bytes) {
    Q_UNUSED(bytes);

    QString content;
    content.append("[Set ID Done]");
    mConversationModel.add(content, COLOR_DEVICE_ID, false);
}

void DebugWindow::onRecvKeyEvent(QByteArray& bytes) {
    QString content;
    content.append("[Key]\n");
    content.append(bytesToString(bytes.mid(1)));
    mConversationModel.add(content, COLOR_KEY_EVENT, false);
}

void DebugWindow::onRecvLog(QByteArray& bytes) {
    QString content;
    content.append("[Log]\n");
    content.append(QString(bytes.mid(1)));
    mConversationModel.add(content, COLOR_LOG, false);
}

void DebugWindow::onRecvLoopback(QByteArray& bytes) {
    QString content;
    content.append("[Loopback Done]\n");
    content.append(bytesToString(bytes.mid(1)));
    mConversationModel.add(content, COLOR_LOOPBACK, false);
}

void DebugWindow::onRecvUnknown(QByteArray& bytes) {
    QString content;
    content.append("[Unknown]\n");
    content.append(bytesToString(bytes));
    mConversationModel.add(content, COLOR_UNKNOWN, false);
}

QString DebugWindow::bytesToString(const QByteArray& bytes) {
    QStringList strList;
    for (int i=0; i<bytes.size(); i++) {
        strList << QString::asprintf("%02X", static_cast<quint8>(bytes[i]));
    }
    return strList.join(" ");
}
